using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Reflection;
using System.Collections;
using Microsoft.SharedSource.SqlServer.MDDE.Repository;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SharedSource.SqlServer.MDDE.Components;


namespace Microsoft.SharedSource.SqlServer.MDDE.Middleware
{
    public enum UpgradeStatus
    {
        Started,
        Sucess,
        Fail
    }

    public struct UpgradeData
    {
        public string oldName;
        public string newName;
        public XmlDocument changeXml;
    }

    public static partial class MDDEManager
    {
        #region Public Methods

        #region Events

        public delegate void UpgradeStatusDeletgate(string name, UpgradeStatus status, List<string> log);

        public static event MDDEManager.UpgradeStatusDeletgate UpgradePackageStatusEvent;
        public static event MDDEManager.UpgradeStatusDeletgate UpgradeInstanceStatusEvent;

        #endregion
        /// <summary>
        /// Adds a new template into the repository.
        /// </summary>
        /// <param name="connectionString"></param>
        /// <param name="templateName"></param>
        /// <param name="templateBody"></param>
        /// <param name="templateConfigurations"></param>
        /// <returns></returns>
        public static OperationResult AddNewTemplate(
            string connectionString, string templateName,
            string templateBody, XmlDocument templateConfigurations)
        {
            OperationResult result = OperationResult.Empty;

            try
            {
                // Create a repository object.
                MDDERepository repository =
                    new MDDERepository(connectionString);

                // Create a package template.
                MDDEPackageTemplate template =
                    new MDDEPackageTemplate(repository, templateName);

                // Provide the template body.
                template.Body = templateBody;

                #region Populate the template configurable components.

                foreach (XmlNode configurableComponent in
                    templateConfigurations.SelectNodes("//ConfigurableComponent"))
                {
                    // Create a new configurable component.
                    TemplateConfigurableComponent objComponent =
                        new TemplateConfigurableComponent(
                        template, configurableComponent.Attributes["ComponentUri"].Value);

                    // Fill in component properties.
                    objComponent.ComponentClassId =
                        configurableComponent.Attributes["ComponentClassId"].Value;

                    objComponent.ComponentName =
                        configurableComponent.Attributes["ComponentName"].Value;

                    objComponent.ComponentType =
                        configurableComponent.Attributes["ComponentType"].Value;

                    objComponent.ComponentUri =
                        configurableComponent.Attributes["ComponentUri"].Value;

                    objComponent.ContainsMappings =
                        configurableComponent.
                        Attributes["ContainsMappings"].Value;

                    objComponent.ContainsCollections =
                        configurableComponent.
                        Attributes["ContainsCollections"].Value;

                    // Fill in component scalar properties.
                    foreach (XmlNode prop in
                        configurableComponent.SelectNodes("./ConfigurableProperty"))
                    {
                        // Create a new property.
                        TemplateScalarProperty objProp =
                            new TemplateScalarProperty(objComponent,
                            prop.Attributes["Name"].Value);

                        //objProp.PropertyName =
                        //    prop.Attributes["Name"].Value;

                        // TODO: Provide this value.
                        objProp.Type = "Unknown";

                        // Append this property to component scalar properties.
                        objComponent.ScalarProperties.Add(objProp);
                    }

                    // Append this configurable component to template.
                    template.ConfigurableComponents.Add(objComponent);
                }

                #endregion

                // Create the template.
                template.Create();

                // Enter success status into the log.
                result.SuccessStatus = true;
                result.OperationLog.Add(
                    "Successful template import: Connection - " +
                    connectionString + " Template - " + templateName);
            }
            catch (Exception ex)
            {
                result.SuccessStatus = false;
                result.OperationLog.Add(
                    "Template import failed: Connection - " +
                    connectionString + " Template - " + templateName +
                    " Error - " + ex.Message);
            }

            return result;
        }

        public static OperationResult UpdateTemplateConfigurations(
            string connectionString, string templateName,
            XmlDocument templateConfigurations)
        {
            OperationResult result = OperationResult.Empty;

            try
            {
                // Create a repository object.
                MDDERepository repository =
                    new MDDERepository(connectionString);

                // Create a package template.
                MDDEPackageTemplate template =
                    new MDDEPackageTemplate(repository, templateName);

                // Remove all the template configurable components.
                template.ConfigurableComponents.Clear();

                // Add fresh template configurable components.
                foreach (XmlNode configurableComponent in
                    templateConfigurations.
                    SelectNodes("//ConfigurableComponent"))
                {
                    // Create a new configurable component.
                    TemplateConfigurableComponent objComponent =
                        new TemplateConfigurableComponent(
                        template,
                        configurableComponent.
                        Attributes["ComponentUri"].Value);

                    // Overwrite the component properties.
                    objComponent.ComponentClassId =
                        configurableComponent.
                        Attributes["ComponentClassId"].Value;

                    objComponent.ComponentName =
                        configurableComponent.
                        Attributes["ComponentName"].Value;

                    objComponent.ComponentType =
                        configurableComponent.
                        Attributes["ComponentType"].Value;

                    objComponent.ComponentUri =
                        configurableComponent.
                        Attributes["ComponentUri"].Value;

                    objComponent.ContainsMappings =
                        configurableComponent.
                        Attributes["ContainsMappings"].Value;

                    objComponent.ContainsCollections =
                        configurableComponent.
                        Attributes["ContainsCollections"].Value;

                    // Clear earlier properties if any.
                    objComponent.ScalarProperties.Clear();

                    // Fill in fresh scalar properties.
                    foreach (XmlNode prop in
                        configurableComponent.
                        SelectNodes("./ConfigurableProperty"))
                    {
                        // Create a new property.
                        TemplateScalarProperty objProp =
                            new TemplateScalarProperty(objComponent,
                            prop.Attributes["Name"].Value);

                        // TODO: Provide this value.
                        objProp.Type = "Unknown";

                        // Append this property to component scalar properties.
                        objComponent.ScalarProperties.Add(objProp);
                    }

                    // Append this configurable component to template.
                    template.ConfigurableComponents.Add(objComponent);
                }

                // Update the template.
                template.Update();

                // Enter success status into the log.
                result.SuccessStatus = true;
                result.OperationLog.Add(
                    "Successful template update: Connection - " +
                    connectionString + " Template - " + templateName);
            }
            catch (Exception ex)
            {
                result.SuccessStatus = false;
                result.OperationLog.Add(
                    "Template udpate failed: Connection - " +
                    connectionString + " Template - " + templateName +
                    " Error - " + ex.Message);
            }

            return result;
        }

        public static OperationResult DeleteTemplate(
            string repositoryConnectionString, string templateName)
        {
            OperationResult result = OperationResult.Empty;

            // Delete the package.
            try
            {

                MDDERepository repository =
                    new MDDERepository(repositoryConnectionString);

                if (!repository.PackageTemplates.
                    ContainsTemplate(templateName))
                {
                    result.OperationLog.Add("Template \"" + templateName +
                        "\" doesn't exists in the repository");

                    return result;
                }

                MDDEPackageTemplate template =
                    repository.PackageTemplates[templateName];

                template.Delete();

                result.SuccessStatus = true;
            }
            catch (Exception ex)
            {
                result.OperationLog.Add(ex.Message);
            }
            return result;
        }

        public static OperationResult GetTemplateConfigurableComponents(
            string repositoryConnectionString, string templateName)
        {
            OperationResult result = OperationResult.Empty;
            try
            {
                // Create a repository object.
                MDDERepository repository =
                    new MDDERepository(repositoryConnectionString);

                // Create a package template.
                MDDEPackageTemplate template =
                    new MDDEPackageTemplate(repository, templateName);

                // Enter success status into the log.
                result.SuccessStatus = true;
                result.OperationLog.Add("Success");
                result.OperationOutputParameters.Add(template.Configuration);
            }
            catch (Exception ex)
            {
                result.SuccessStatus = false;
                result.OperationLog.Add(ex.Message);
            }
            return result;
        }


        public static OperationResult GetTemplateDifferences(string connectionString,
            string oldTemplateName, string templateBody)
        {
            OperationResult result = OperationResult.Empty;

            try
            {
                // Create a repository object.
                MDDERepository repository = new MDDERepository(connectionString);

                //Check if oldTemplate exists in reporsitory, get the template if it exists.
                if (!repository.PackageTemplates.ContainsTemplate(oldTemplateName))
                {
                    result.SuccessStatus = false;
                    result.OperationLog.Add(
                        "Template " + oldTemplateName + " doesn't exists in given repository");
                    return result;
                }
                MDDEPackageTemplate template =
                       new MDDEPackageTemplate(repository, oldTemplateName);



                // We assume that TemplateInstance and Package Collection  are being ignored at 
                //the time of Template creation so we need not clear them here.

                //Get the difference between old and new templates.
                Package oldTemplatePackage = new Package();
                oldTemplatePackage.LoadFromXML(template.Body, null);
                MDDEExecutionDAG oldTemplateDag = new MDDEExecutionDAG(oldTemplatePackage);

                Package newTemplatePackage = new Package();
                newTemplatePackage.LoadFromXML(templateBody, null);
                MDDEExecutionDAG newTemplateDag = new MDDEExecutionDAG(newTemplatePackage);

                XmlDocument differenceReport = oldTemplateDag.GetDifference(newTemplateDag);

                //Upgrade the configuration of old template and store that with new template.
                XmlNode deletedComponents =
                    differenceReport.SelectSingleNode("/PackageDifference/DeletedComponent");

                foreach (XmlNode deletedComponent in deletedComponents.ChildNodes)
                {
                    if (template.ConfigurableComponents.
                        ContainsConfigurableComponent(deletedComponent.Attributes["Uri"].Value))
                    {
                        template.ConfigurableComponents.Remove(template.
                            ConfigurableComponents[deletedComponent.Attributes["Uri"].Value]);
                    }
                }

                XmlDocument changeXml = newTemplateDag.GetChangeXml(oldTemplateDag);

                result.SuccessStatus = true;
                result.OperationOutputParameters.Add(differenceReport);
                result.OperationOutputParameters.Add(changeXml);
                result.OperationOutputParameters.Add(template.Configuration);
            }
            catch (Exception ex)
            {
                result.SuccessStatus = false;
                result.OperationLog.Add(
                    "Template difference failed: Connection - " + connectionString +
                    " Old Template - " + oldTemplateName + " Error - " + ex.Message);
            }
            return result;
        }

        public static OperationResult UpgradePackages(string connectionString,
            string oldTemplateName, string newTemplateName, UpgradeData[] packages)
        {
            OperationResult result = OperationResult.Empty;
            try
            {
                // Create a repository object.
                MDDERepository repository = new MDDERepository(connectionString);

                //Check if oldTemplate exists in reporsitory, get the template if it exists.
                if (!repository.PackageTemplates.ContainsTemplate(oldTemplateName))
                {
                    result.SuccessStatus = false;
                    result.OperationLog.Add(
                        "Template " + oldTemplateName + " doesn't exists in given repository");
                    return result;
                }

                MDDEPackageTemplate oldTemplate = repository.PackageTemplates[oldTemplateName];

                //Check if newTemplate exists in reporsitory, get the template if it exists.
                if (!repository.PackageTemplates.ContainsTemplate(newTemplateName))
                {
                    result.SuccessStatus = false;
                    result.OperationLog.Add(
                        "Template " + newTemplateName + " doesn't exists in given repository");
                    return result;
                }
                MDDEPackageTemplate newTemplate = repository.PackageTemplates[newTemplateName];

                //Compare both templates
                Package oldTemplatePackage = new Package();
                oldTemplatePackage.LoadFromXML(oldTemplate.Body, null);
                MDDEExecutionDAG oldTemplateDag = new MDDEExecutionDAG(oldTemplatePackage);

                Package newTemplatePackage = new Package();
                newTemplatePackage.LoadFromXML(newTemplate.Body, null);
                MDDEExecutionDAG newTemplateDag = new MDDEExecutionDAG(newTemplatePackage);

                XmlDocument differenceReport = oldTemplateDag.GetDifference(newTemplateDag);

                foreach (UpgradeData package in packages)
                {
                    MDDEManager.UpgradePackageStatusEvent(
                            package.oldName, UpgradeStatus.Started, null);

                    List<string> log = new List<string>();

                    if (!oldTemplate.Packages.ContainsPackage(package.oldName))
                    {
                        log.Add("Package - " + package.oldName +
                            " doesn't belong to Template - " + oldTemplateName);
                        MDDEManager.UpgradePackageStatusEvent(
                            package.oldName, UpgradeStatus.Fail, log);
                        continue;
                    }
                    MDDEPackage mddePackage = oldTemplate.Packages[package.oldName];

                    if (MDDEManager.UpgradePackage(newTemplate, newTemplateDag, mddePackage, 
                        package.newName, package.changeXml, differenceReport, log))
                    {
                        MDDEManager.UpgradePackageStatusEvent(
                            package.oldName, UpgradeStatus.Sucess, log);
                    }
                    else
                    {
                        MDDEManager.UpgradePackageStatusEvent(
                            package.oldName, UpgradeStatus.Fail, log);
                    }
                    result.OperationLog.AddRange(log);
                }
                result.SuccessStatus = true;
            }
            catch (Exception ex)
            {
                result.SuccessStatus = false;
                result.OperationLog.Add(
                    "Packages upgradation failed: Connection - " + connectionString +
                    " Old Template - " + oldTemplateName + " New Template - " + newTemplateName +
                    " Error - " + ex.Message);
            }

            return result;
        }

        private static bool UpgradePackage(MDDEPackageTemplate newTemplate, MDDEExecutionDAG newTemplateDag,
            MDDEPackage mddePackage, string newPackageName, XmlDocument changeXML, XmlDocument differenceReport, List<string> log)
        {
            try
            {
                //Create new package from template
                Package package = new Package();
                package.LoadFromXML(newTemplate.Body, null);
                MDDEExecutionDAG packageDag = new MDDEExecutionDAG(package);

                XmlDocument oldChangeXml = mddePackage.ChangesFromTemplate;

                XmlNode deletedComponents =
                       differenceReport.SelectSingleNode("/PackageDifference/DeletedComponent");

                foreach (XmlNode deletedComponent in deletedComponents.ChildNodes)
                {
                    XmlNode componentNode = oldChangeXml.
                        SelectSingleNode("//*[@URL='" + deletedComponent.Attributes["Uri"].Value + "']");
                    if (null != componentNode)
                        oldChangeXml.FirstChild.RemoveChild(componentNode);
                }
                //Apply change xml retrived from pacakge first
                bool applyChangesResult = packageDag.ApplyChanges(oldChangeXml, newPackageName);

                if (null != changeXML)
                    applyChangesResult = packageDag.ApplyChanges(changeXML);
                //Then apply given change xml
                if (!applyChangesResult)
                {
                    log.Add("Package - " + mddePackage.Name +
                    " Can't be upgraded. Error - Not able to fix the package");
                    return false;
                }
                //Create new changeXml by comparing package with template.
                XmlDocument newChangeXml = packageDag.GetChangeXml(newTemplateDag);

                //Create new MDDEPackage and save that to repository.
                MDDEPackage newPackage = new MDDEPackage(newTemplate, newPackageName);

                string packageBody;
                (packageDag.InnerComponent as Package).SaveToXML(out packageBody, null);

                newPackage.Body = packageBody;
                newPackage.Version = "1.0.0.0";
                newPackage.ChangesFromTemplate = newChangeXml;
                newPackage.Create();
                log.Add("Package - " + mddePackage.Name +
                    " upgraded successfully.");
            }
            catch (Exception ex)
            {
                log.Add("Package - " + mddePackage.Name +
                    " Can't be upgraded. Error - " + ex.Message);
                return false;
            }
            return true;
        }

        public static OperationResult UpgradeInstances(string connectionString,
            string oldTemplateName, string newTemplateName, UpgradeData[] instances)
        {
            OperationResult result = OperationResult.Empty;
            try
            {
                // Create a repository object.
                MDDERepository repository = new MDDERepository(connectionString);

                //Check if oldTemplate exists in reporsitory, get the template if it exists.
                if (!repository.PackageTemplates.ContainsTemplate(oldTemplateName))
                {
                    result.SuccessStatus = false;
                    result.OperationLog.Add(
                        "Template " + oldTemplateName + " doesn't exists in given repository");
                    return result;
                }
                MDDEPackageTemplate oldTemplate =
                       new MDDEPackageTemplate(repository, oldTemplateName);
                //Check if newTemplate exists in reporsitory, get the template if it exists.
                if (!repository.PackageTemplates.ContainsTemplate(newTemplateName))
                {
                    result.SuccessStatus = false;
                    result.OperationLog.Add(
                        "Template " + newTemplateName + " doesn't exists in given repository");
                    return result;
                }
                MDDEPackageTemplate newTemplate =
                       new MDDEPackageTemplate(repository, newTemplateName);

                //Compare both templates
                Package oldTemplatePackage = new Package();
                oldTemplatePackage.LoadFromXML(oldTemplate.Body, null);
                MDDEExecutionDAG oldTemplateDag = new MDDEExecutionDAG(oldTemplatePackage);

                Package newTemplatePackage = new Package();
                newTemplatePackage.LoadFromXML(newTemplate.Body, null);
                MDDEExecutionDAG newTemplateDag = new MDDEExecutionDAG(newTemplatePackage);

                XmlDocument differenceReport = oldTemplateDag.GetDifference(newTemplateDag);

                foreach (UpgradeData instanceData in instances)
                {
                    MDDEManager.UpgradeInstanceStatusEvent(instanceData.oldName, UpgradeStatus.Started, null);
                    List<string> log = new List<string>();

                    if (oldTemplate.TemplateInstances.ContainsTemplateInstance(instanceData.oldName))
                    {
                        MDDETemplateInstance instance = oldTemplate.TemplateInstances[instanceData.oldName];

                        if (MDDEManager.UpgradeInstance(newTemplate, instance, instanceData.newName, instanceData.changeXml, differenceReport, log))
                        {
                            MDDEManager.UpgradeInstanceStatusEvent(instanceData.oldName, UpgradeStatus.Sucess, log);
                        }
                        else
                        {
                            MDDEManager.UpgradeInstanceStatusEvent(instanceData.oldName, UpgradeStatus.Fail, log);
                        }
                    }
                    else
                    {
                        log.Add("Package - " + instanceData.oldName + " doesn't belong to Template - " + oldTemplateName);
                        MDDEManager.UpgradeInstanceStatusEvent(instanceData.oldName, UpgradeStatus.Fail, log);
                    }
                    result.OperationLog.AddRange(log);
                }
                result.SuccessStatus = true;
            }
            catch (Exception ex)
            {
                result.SuccessStatus = false;
                result.OperationLog.Add(
                    "Packages upgradation failed: Connection - " + connectionString +
                    " Old Template - " + oldTemplateName + " New Template - " + newTemplateName +
                    " Error - " + ex.Message);
            }

            return result;
        }

        private static bool UpgradeInstance(MDDEPackageTemplate newTemplate,
            MDDETemplateInstance mddeInstance, string newInstanceName, XmlDocument changeXml,
            XmlDocument differenceReport, List<string> log)
        {
            try
            {
                mddeInstance.ParentTemplate = newTemplate;

                XmlNode deletedComponents =
                           differenceReport.SelectSingleNode("/PackageDifference/DeletedComponent");

                foreach (XmlNode deletedComponent in deletedComponents.ChildNodes)
                {
                    if (mddeInstance.Components.
                        ContainsConfigurableComponent(deletedComponent.Attributes["Uri"].Value))
                    {
                        mddeInstance.Components.Remove(
                            mddeInstance.Components[deletedComponent.Attributes["Uri"].Value]);
                    }
                }
                if (null != changeXml)
                    mddeInstance.ApplyProperties(changeXml, false);

                mddeInstance.Create();
                log.Add("Instance - " + mddeInstance.Name +
                    " upgraded successfully.");
            }
            catch (Exception ex)
            {
                log.Add("Instance - " + mddeInstance.Name +
                    " Can't be upgraded. Error - " + ex.Message);
                return false;
            }

            return true;
        }

        #endregion
    }
}
